package src.World;

import java.io.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.*;
import src.Main;

public class Map
{
    public int maxObjects = 747531;
    private MapObject[] mo;

    private String worldMapDir = "src/Data/WorldMap";

    private String worldMapFiles[] = {
        "World1.xml", "World2.xml", "World3.xml",
       "World4.xml", "World5.xml", "World6.xml",
       "World7.xml", "World8.xml", "World9.xml",
       "World10.xml", "World11.xml", "World12.xml",
       "World13.xml", "World14.xml", "World15.xml",
       "World16.xml", "World17.xml", "World18.xml",
       "World19.xml", "World20.xml", "World21.xml",
       "World22.xml", "World23.xml", "World24.xml",
       "World25.xml"
    };

    private String worldMapNames[] = new String[worldMapFiles.length];
    private Document worldMaps[] = new Document[worldMapFiles.length];
    private int cWorldMap = 0;
    private int cWorldMap2 = 0;
    private boolean hasLoaded = false;

    public void loadWorldMap()
    {
        mo = new MapObject[maxObjects];
        try
        {
            for(int i = 0; i < worldMapFiles.length; i++)
            {
                Main.m.log("[NOTICE] Loading world map " + i + "...");
                if(prepareWorldMap(worldMapFiles[i]))
                {
                    loadWorldMap(worldMaps[i]);
                    releaseWorldMap(worldMapNames[i]);
                    Main.m.log("[NOTICE] World map " + i + " loaded.");
                }
            }
        }
        catch (Exception _ex)
        {
            Main.m.log("[ERROR] Error while loading world maps");
        }
        hasLoaded = true;
        Main.m.log("[LOAD] Object count " + cWorldMap2);
    }

    private boolean prepareWorldMap(String fileName)
    {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        File dbFile = new File("./" + worldMapDir + "/" + fileName);
        if(!dbFile.exists() || fileName.indexOf(".xml") < 0)
        {
            Main.m.log("[ERROR]: Unable to load world map: " + fileName);
            return false;
        }
        try
        {
            DocumentBuilder db = dbf.newDocumentBuilder();
            worldMaps[cWorldMap] = db.parse("./" + worldMapDir + "/" + fileName);
            worldMapNames[cWorldMap] = fileName.substring(0, fileName.indexOf("."));
        }
        catch (Exception _ex)
        {
            return false;
        }
        cWorldMap++;
        return true;
    }

    private boolean releaseWorldMap(String worldMapName)
    {
        for(int i = 0; i < worldMapNames.length; i++)
        {
            if(worldMapNames[i].equalsIgnoreCase(worldMapName))
            {
                worldMaps[i] = null;
                return true;
            }
        }
        return false;
    }

    private void loadWorldMap(Document worldMap)
    {
        if(worldMap == null)
        {
            Main.m.log("[ERROR] world map not found");
            return;
        }
        NodeList worldNl = worldMap.getElementsByTagName("worlddb");
        if(worldNl.item(0) == null)
        {
            return;
        }
        Element worldMaster = (Element) worldNl.item(0);
        Element worldElement = worldMap.getDocumentElement();
        NodeList nl = worldElement.getElementsByTagName("tile");
        if(nl != null && nl.getLength() > 0)
        {
            for(int i = 0; i < nl.getLength(); i++)
            {
                try
                {
                    Element tile = (Element) nl.item(i);
                    if(tile == null)
                        continue;
                    int height = Integer.parseInt(tile.getAttribute("height"));
                    if(height == -1)
                        continue;
                    int tileX = Integer.parseInt(tile.getAttribute("x"));
                    int tileY = Integer.parseInt(tile.getAttribute("y"));
                    int type = getNumberValue(tile, "type");
                    if(type >= 4 && type <= 8 || type >= 12 && type <= 22)
                        continue;
                    int object = getNumberValue(tile, "object");
                    if(object <= 0)
                        continue;
                    int orientation = getNumberValue(tile, "orientation");
                    addObject(tileX, tileY, height, type, object, orientation);
                }
                catch (Exception _ex)
                {
                    Main.m.log("[ERROR] loading worldobject(" + i + ") failed");
                    continue;
                }
            }
        }
    }

    private int getNumberValue(Element ele, String tagName)
    {
        try
        {
            int areturn = Integer.parseInt(getTextValue(ele, tagName));
            return areturn;
        }
        catch (Exception e)
        {
            return -1;
        }
    }

    private String getTextValue(Element ele, String tagName)
    {
        String textVal = null;
        NodeList nl = ele.getElementsByTagName(tagName);
        if(nl != null && nl.getLength() > 0)
        {
            Element el = (Element) nl.item(0);
            textVal = el.getFirstChild().getNodeValue();
        }
        return textVal;
    }

    private void addObject(int tileX, int tileY, int height, int type, int object, int orientation)
    {
        mo[cWorldMap2++] = new MapObject(object, tileX, tileY, height);
        //mo[cWorldMap2].objectType = type;
        //mo[cWorldMap2++].objectFace = orientation;
    }

    public boolean isWalkable(int height, int absX, int absY, int toAbsX, int toAbsY)
    {
        if(!hasLoaded)
        {
            return true;
        }
        if(absX == toAbsX && absY == toAbsY)
        {
            return true;
        }
        int direction1 = getDirection(absX, absY, toAbsX, toAbsY);
        int direction2 = getOppositeDirection(absX, absY, toAbsX, toAbsY);
        if(direction1 == -1 || direction2 == -1)
        {
            return false;
        }
        for(int i = 0; i < maxObjects; i++)
        {
            if(mo[i] == null)
                continue;
            if(mo[i].getHeight() != height)
                continue;
            if(mo[i].getX() == toAbsX && mo[i].getY() == toAbsY) 
		return false;
        }
	return true;
    }

    private int getOppositeDirection(int absX, int absY, int toAbsX, int toAbsY)
    {
        if((absX - 1) == toAbsX && absY == toAbsY)
            return 2;
        if((absX + 1) == toAbsX && absY == toAbsY)
            return 0;
        if(absX == toAbsX && (absY - 1) == toAbsY)
            return 1;
        if(absX == toAbsX && (absY + 1) == toAbsY)
            return 3;
        if((absX - 1) == toAbsX && (absY - 1) == toAbsY)
            return 11;
        if((absX + 1) == toAbsX && (absY - 1) == toAbsY)
            return 10;
        if((absX - 1) == toAbsX && (absY + 1) == toAbsY)
            return 8;
        if((absX + 1) == toAbsX && (absY + 1) == toAbsY)
            return 9;
	if(absY == toAbsY && (absX - 1) == toAbsX)
            return 12;
        if(absY == toAbsY && (absX + 1) == toAbsX)
            return 13;
        return -1;
    }

    private int getDirection(int absX, int absY, int toAbsX, int toAbsY)
    {
        if((absX - 1) == toAbsX && absY == toAbsY)
            return 0;
        if((absX + 1) == toAbsX && absY == toAbsY)
            return 2;
        if(absX == toAbsX && (absY - 1) == toAbsY)
            return 3;
        if(absX == toAbsX && (absY + 1) == toAbsY)
            return 1;
        if((absX - 1) == toAbsX && (absY - 1) == toAbsY)
            return 9;
        if((absX + 1) == toAbsX && (absY - 1) == toAbsY)
            return 8;
        if((absX - 1) == toAbsX && (absY + 1) == toAbsY)
            return 10;
        if((absX + 1) == toAbsX && (absY + 1) == toAbsY)
            return 11;
        if(absY == toAbsY && (absX - 1) == toAbsX)
            return 12;
        if(absY == toAbsY && (absX + 1) == toAbsX)
            return 13;
        return -1;
    }
}